#include <ostream>
_Pragma("once")

#include "pch.hpp"
#include "log.hpp"

namespace sdl_engine {

/*
 * @warn don't do this: `for (int i = 0; i < GetPlaybackAudioDeviceNum().value(); i++)`
 *      do this: `int num = GetPlaybackAudioDeviceNum();`, then use num in for-loop.
 */
inline std::optional<int> GetPlaybackAudioDeviceNum() {
    int num = SDL_GetNumAudioDevices(0);
    return num != -1 ? std::optional<int>(num) : std::nullopt;
}

/*
 * @warn don't do this: `for (int i = 0; i < GetRecordAudioDeviceNum().value(); i++)`
 *      do this: `int num = GetRecordAudioDeviceNum();`, then use num in for-loop.
 */
inline std::optional<int> GetRecordDeviceDeviceNum() {
    int num = SDL_GetNumAudioDevices(1);
    return num != -1 ? std::optional<int>(num) : std::nullopt;
}

/* @param idx: the index of device(from 0 to GetPlaybackAudioDeviceNum() - 1) */
inline std::string GetPlaybackAudioDeviceName(int idx) {
    return SDL_GetAudioDeviceName(idx, 0);
}

/* @param idx: the index of device(from 0 to GetRecordAudioDeviceNum() - 1) */
inline std::string GetRecordAudioDeviceName(int idx) {
    return SDL_GetAudioDeviceName(idx, 1);
}

using AudioCallback = void(*)(void*, u8*, int);

struct AudioConfig {
    friend class AudioDevice;

    enum ChannelType {
        MONO = 1,
        STEREO = 2,
        QUAD = 4,
        CHANNEL51 = 6,
    };

    AudioConfig();
    AudioConfig(const SDL_AudioSpec&);

    inline AudioConfig& SetSampleRate(int s) {
        spec_.samples = s;
        return *this;
    }
    inline u16 GetSampleRate() { return spec_.samples; }

    inline AudioConfig& SetChannel(ChannelType type) {
        spec_.channels = type;
        return *this;
    }

    inline ChannelType GetChannel() {
        return static_cast<ChannelType>(spec_.channels);
    }

    inline AudioConfig& SetFreq(int freq) {
        spec_.freq = freq;
        if (freq > 48000) {
            Loge("freq > 48000 may cause some problem, use more memory and CPU");
        }
        return *this;
    }
    inline int GetFreq() { return spec_.freq; }

    SDL_AudioSpec& GetRaw() { return spec_; }

    inline AudioConfig& SetFormat(SDL_AudioFormat format) {
        spec_.format = format;
        return *this;
    }
    inline SDL_AudioFormat GetFormat() { return spec_.format; }

    inline AudioConfig& SetUserdata(void* data) {
        spec_.userdata = data;
        return *this;
    }
    inline void* GetUserdata() { return spec_.userdata; }

    inline AudioConfig& SetCallback(AudioCallback cb) {
        spec_.callback = cb;
        return *this;
    }

    /* after pass to function, it will be calculated */
    inline u8 GetSilence() const { return spec_.silence; }
    /* after pass to function, it will be calculated */
    inline u32 GetSize() const { return spec_.size; }

    friend std::ostream& operator<<(std::ostream&, const AudioConfig&);

private:
    SDL_AudioSpec spec_;
};

std::ostream& operator<<(std::ostream&, const AudioConfig&);



class AudioDevice {
public:
    using ID = SDL_AudioDeviceID;

    AudioDevice(AudioConfig, bool playbackAudio);
    AudioDevice(bool playbackAudio, AudioCallback cb, void* userdata = nullptr);
    // @warning don't use this function unless you know what you're doing.
    AudioDevice(const std::string& name, AudioConfig, bool playbackAudio);
    AudioDevice(const AudioDevice&) = delete;
    ~AudioDevice();

    void Start();
    void Stop();
    inline ID GetID() const { return id_; }
    inline const AudioConfig& GetConfig() const { return config_; }

    AudioDevice& operator=(const AudioDevice&) = delete;

private:
    // negative means invalid ID. 1 means default device(opend by `SDL_OpenAudio`)
    // >= 2 means specific device(opend by `SDL_OpenAudioDevice`).
    ID id_ = -1;
    AudioConfig config_;

    void openDefaultAudio(AudioConfig&);
    void openAudioDevice(AudioConfig&, bool playback, const std::string& name);
};

inline void MixAudio(u8* dst, const u8* src, u32 len, int volume) {
    SDL_MixAudio(dst, src, len, volume);
}

class AudioSample {
public:
    AudioSample(u8* data, u32 size, const AudioConfig&);
    AudioSample(const std::string& filename);
    AudioSample(const AudioSample&) = delete;
    ~AudioSample();

    AudioSample& operator=(const AudioSample&) = delete;

    inline bool Valid() const {
        return data_ && size_ != 0;
    }

    inline int64_t GetSize() const { return size_; }
    inline u8* GetData() const { return data_; }

    inline operator bool() const {
        return Valid();
    }

    AudioConfig& GetConfig() { return config_; }

private:
    u8* data_ = nullptr;
    int64_t size_ = 0;
    AudioConfig config_;
};

};
